/*
 * Package il.ac.biu.cs.grossmm.test.cppToFooAdapter
 * File PresencePublicationAdapter.java
 * 
 * This is a part of presence service framework API. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.tests.fooAdapters;

import static il.ac.biu.cs.grossmm.api.data.NodeTypeByInterface.*;
import static il.ac.biu.cs.grossmm.api.keys.KeyFactory.*;
import static il.ac.biu.cs.grossmm.api.keys.PatternFactory.*;
import static il.ac.biu.cs.grossmm.api.presence.BaseVocabulary.*;
import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.Node;
import il.ac.biu.cs.grossmm.api.data.Root;
import il.ac.biu.cs.grossmm.api.flow.DataProcessorBase;
import il.ac.biu.cs.grossmm.api.flow.NodeManipulator;
import il.ac.biu.cs.grossmm.api.flow.PublicationPoint;
import il.ac.biu.cs.grossmm.api.keys.Key;
import il.ac.biu.cs.grossmm.api.keys.KeyPattern;
import il.ac.biu.cs.grossmm.api.presence.Entity;
import il.ac.biu.cs.grossmm.api.presence.EntityType;
import il.ac.biu.cs.grossmm.api.presence.Policy;
import il.ac.biu.cs.grossmm.api.presence.Registrar;
import il.ac.biu.cs.grossmm.api.presence.ResourceType;
import il.ac.biu.cs.grossmm.api.server.Component;
import il.ac.biu.cs.grossmm.foo.format.PersonalRules;
import il.ac.biu.cs.grossmm.foo.format.ServiceRules;
import il.ac.biu.cs.grossmm.foo.format.WatcherRules;
import il.ac.biu.cs.grossmm.foo.format.WatcherRulesDoc;
import il.ac.biu.cs.grossmm.foo.format.WatcherRulesDoc.PersonalRule;
import il.ac.biu.cs.grossmm.foo.format.WatcherRulesDoc.ServiceRule;
import il.ac.biu.cs.grossmm.foo.protocol.PublishRequest;
import il.ac.biu.cs.grossmm.foo.protocol.Request;
import il.ac.biu.cs.grossmm.foo.protocol.Response;
import il.ac.biu.cs.grossmm.foo.protocol.ResponseCodes;
import il.ac.biu.cs.grossmm.foo.protocol.Session;
import il.ac.biu.cs.grossmm.foo.protocol.UserSession;
import il.ac.biu.cs.grossmm.api.server.Service;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class RulesPublicationAdapter extends DataProcessorBase implements
	Service, Component, RequestHandler {
	Registrar registrar;

	FooProvider provider;

	PublicationPoint<WatcherRules> pp;

	public RulesPublicationAdapter()
	{
		super("Watcher Rules Publication Adapter");
	}

	class Publication {
		String pid;

		Node<WatcherRules> node;

		/**
		 * @param pid
		 * @param root
		 */
		public Publication(String pid, Node<WatcherRules> node)
		{
			super();
			this.pid = pid;
			this.node = node;
		}

	}
	

	@Override
	public void run()
	{
		KeyPattern pattern =
			pattern(Entity.class, mandatory(RESOURCE_TYPE,
				Policy.policy("foo-watcher-rules")));
		
		try {
			registrar = componentManager.getByClass(Registrar.class);
			provider = componentManager.getByClass(FooProvider.class);

			pp =
				adm.createPublicationPoint(pattern,
					nodeType(WatcherRules.class), false, getClass().getName());

		} catch (Exception e) {
			componentManager.abort(e);
			stopped();
			return;
		}

		provider.registerRequestHandler(PublishRequest.class, this);

		started();
	}

	public void attached(UserSession session)
	{

	}

	public void dettached(UserSession session)
	{
		// TODO
	}

	public synchronized boolean requestReceived(Session session, Request request)
		throws OperationFailedException
	{
		assert request instanceof PublishRequest;

		PublishRequest pub = (PublishRequest) request;

		if (!pub.getPayloadType().equals("foo.WatcherRulesDoc"))
			return false;

		String user = pub.getPresentity();
		UserSession u = session.getUserSession(user);
		if (u == null) {
			session.send(new Response(request, ResponseCodes.UNAUTHORIZED));
			return true;
		}

		Entity presentity = u.getEntity();

		if (!presentity.is(EntityType.PRESENTITY)) {
			session.send(new Response(request, ResponseCodes.FORBIDDEN));
			return true;
		}

		String pid = pub.getPid();

		if (! pid.equals("0")) {
			session.send(new Response(request, ResponseCodes.NOT_FOUND,
				"Only pid=0 is supported for watcher rules"));
			return true;
		}

		Root<WatcherRules> root;
		Node lockNode = null;
		NodeManipulator man = null;

		try {

			Key key =
				key(presentity, entry(RESOURCE_TYPE, Policy.policy("foo-watcher-rules")));

			root = pp.createNode(key, true);

			man = pp.writeLock(lockNode = root);

			WatcherRulesDoc doc = new WatcherRulesDoc(pub.getPayload());

			updateNode(root, man, doc);

		} finally {
			if (man != null)
				pp.writeUnlock(lockNode);
		}

		session.send(request.okResponse());
		return true;
	}

	/**
	 * @param root
	 * @param man
	 * @param doc
	 * @throws OperationFailedException
	 */
	private void updateNode(Node<WatcherRules> root, NodeManipulator man,
		WatcherRulesDoc doc) throws OperationFailedException
	{
		Map<StringPair, Node<PersonalRules>> pmap =
			new HashMap<StringPair, Node<PersonalRules>>();
		
		Map<StringTriple, Node<ServiceRules>> smap =
			new HashMap<StringTriple, Node<ServiceRules>>();

		Set<Node<PersonalRules>> prules =
			man.getSubnodes(root, WatcherRules.PERSONAL_RULES);
		
		Set<Node<ServiceRules>> srules =
			man.getSubnodes(root, WatcherRules.SERVICE_RULES);

		// index currently previously published personal rules
		for (Node<PersonalRules> prule : prules) {
			String sphere = man.getValue(prule, PersonalRules.SPHERE);
			String watcher = man.getValue(prule, PersonalRules.WATCHER);

			pmap.put(new StringPair( sphere, watcher ), prule);
		}

		// index currently previously published service rules
		for (Node<ServiceRules> srule : srules) {
			String sphere = man.getValue(srule, ServiceRules.SPHERE);
			String watcher = man.getValue(srule, ServiceRules.WATCHER);
			String tc = man.getValue(srule, ServiceRules.CLASS);

			smap.put(new StringTriple( sphere, watcher, tc ), srule);
		}

		// set of all document personal rules 
		Set<PersonalRule> dprules = doc.getPersonalRules();
		
		// set of all document service rules
		Set<ServiceRule> dsrules = doc.getServiceRules();

		// set of all previously published presence rules
		Set<Node<PersonalRules>> pset =
			new HashSet<Node<PersonalRules>>(pmap.values());
		
		// set of all previously published service rules
		Set<Node<ServiceRules>> sset =
			new HashSet<Node<ServiceRules>>(smap.values());

		// for all personal rules in the document
		for (PersonalRule pr : dprules) {
			Node<PersonalRules> node =
				pmap.get(new StringPair ( pr.sphere, pr.watcher ));

			// was the rule for the same (sphere, watcher) already published?
			if (node == null)
				// no -> create it
				node = man.createSubnode(root, WatcherRules.PERSONAL_RULES);
			else
				// yes -> remove from pset
				pset.remove(node);

			man.setAndCheck(node, PersonalRules.ALLOW_ACCESS, pr.allowAccess);
			man.setAndCheck(node, PersonalRules.SHOW_ACTIVITIES,
				pr.showActivities);
			man.setAndCheck(node, PersonalRules.SHOW_LOCATION, pr.showLocation);
			man.setAndCheck(node, PersonalRules.SHOW_MOOD, pr.showMood);
			man.setAndCheck(node, PersonalRules.SHOW_SPHERE, pr.showSphere);
		}

		for (ServiceRule sr : dsrules) {
			Node<ServiceRules> node =
				smap.get(new StringTriple( sr.sphere, sr.watcher, sr.tupleClass ));

			if (node == null)
				node = man.createSubnode(root, WatcherRules.SERVICE_RULES);
			else
				sset.remove(node);

			man.setAndCheck(node, ServiceRules.SHOW_CAPS, sr.showCapabilities);
			man.setAndCheck(node, ServiceRules.SHOW_CLASS, sr.showClass);
			man.setAndCheck(node, ServiceRules.SHOW_PRESENCE, sr.showPresence);
		}

		for (Node<PersonalRules> node : pset) {
			man.removeSubnode(node);
		}

		for (Node<ServiceRules> node : sset) {
			man.removeSubnode(node);
		}

	}

	public void terminating(Session session)
	{
		System.out.println("Session closed");
	}
}
